<template>
  <div>
    <!-- <h1>{{ state.name }}</h1>
    <h2>{{ state.age }}</h2> -->
    <!-- <h1>{{ name }}</h1>
    <h2>{{ age }}</h2> -->

    <!-- <span>{{ text }}</span>
    <input type="text" v-model="text" /> -->
  </div>
</template>

<script>
import {
  reactive,
  ref,
  unref,
  isRef,
  toRef,
  toRefs,
  customRef,
  shallowRef,
  triggerRef,
  isReactive,
  readonly,
  watchEffect
} from 'vue';
import Test from '@/components/Test';

function useDoSth(name) {
  return `name is ${name.value}.`
}

function useDebounce(value, delay = 200) { // 通过customRef(实现防抖函数)
  let t = null;
  // customRef 期望一个工厂函数 接收 track(依赖)、trigger(追踪)作为参数
  return customRef((track, trigger) => {
    return { // 会返回一个对象 包含 get、set方法
      get() {
        track(); // 追踪依赖
        return value;
      },
      set(newVal) {
        clearTimeout(t);
        t = setTimeout(() => {
          value = newVal;
          trigger(); // 一更新就要触发
        }, delay);
      }
    }
  })
}

export default {
  name: 'App',
  components: {
    Test
  },
  setup(props, ctx) { // 入口函数，

    // vue3的 reactive(proxy) 相同于 vue2的 Vue.observable() -> Object.defineProperty
    const proxyObj = reactive({ // deep 深度响应的
      a: 1,
      b: {
        c: 2,
        d: [1, 2, 3, 4, 5],
        e: {
          f: 6,
          g: [2, 3, 4],
          h: {
            i: 111,
            j: '123'
          }
        }
      }
    })
    // console.log(proxyObj);

    // --------------------------------------------------------

    // ref 获取一个内部值并返回一个被动的、可变的ref对象，该对象只有一个属性。指向内部值的值。
    const count = ref(0);
    // console.log(count); // 只有.value 一个属性
    // count.value = 1;
    // console.log(count.value);

    const refObj = ref({
      a: 1,
      b: 2
    })
    const refArr = ref([1, 2, 3])
    // 如果将一个对象指定为ref的value，那么将会使用reactive方法使这个对象成为深度的响应式数据
    // console.log(refObj.a); // undefined 无意义使用ref定义对象类型数据
    // console.log(refObj.value); // 推荐还是使用reactive
    // console.log(refArr.value);

    // 如果ref对象被作为 reactive对象的一个属性，则ref的inner value会被展开
    // const state = reactive({
    //   count
    // });
    // console.log(state.count); // 0
    // 虽然被reactive包裹 但原来的ref 的值也会改变
    // state.count = 2;
    // console.log(state.count); // 2
    // console.log(count.value); // 2

    // 如果将一个新的ref分配给链接到现有ref的属性的reactive对象时，它将替换旧的ref

    // const otherCount = ref(3);
    // state.count = otherCount;
    // console.log(state.count); // 3
    // console.log(count.value); // 2

    // 当ref 被作为 reactive 的一个属性时，这个ref不被展开的唯一情况 就是ref被作为数组的一个值或者是原生集合类型的值

    // const arr = reactive([ref(333)]);
    // console.log(arr[0].value);
    // const map = reactive(new Map([['123', ref(333)]]))
    // console.log(map.get('123').value);

    // --------------------------------------------------------

    // isRef 检查值是否为一个ref对象

    // console.log(isRef(count)); // true
    // console.log(isRef(proxyObj)); // false

    // --------------------------------------------------------

    // unref 如果参数是一个 ref，则返回内部值，否则返回参数本身
    // 是下列判断式的语法糖

    // const info1 = {
    //   name: '张三'
    // }
    // const info2 = ref({
    //   name: '张三'
    // })
    // const obj1 = isRef(info1) ? info1.value : info1;
    // console.log(obj1);
    // const obj2 = isRef(info2) ? info2.value : info2;
    // console.log(obj2);
    // const obj3 = unref(info1);
    // const obj3 = unref(info2);
    // console.log(obj3);

    // isRef(info2) ? info2.value : info2 === unref(info2)

    // --------------------------------------------------------

    // toRef(用的比较少, 针对响应式数据)
    // 可以用来为源响应式对象上的某个 property 新创建一个 ref。
    // ref 可以被传递，它会保持对其源 property 的响应式连接

    // const state = reactive({
    //   name: '张三',
    //   age: 23
    // });

    // const nameRef = toRef(state, 'name');
    // console.log(nameRef.value); // 张三

    // nameRef.value = '李四';
    // console.log(nameRef.value); // 李四
    // console.log(state.name); // 李四

    // state.name = '王五';
    // console.log(nameRef.value); // 王五
    // console.log(state.name); // 王五

    // 如果将props里的单个属性传给函数做参数 或 扩展函数时 toRef 很有用

    // const sentence = useDoSth(nameRef);
    // console.log(sentence);

    // or

    // func(toRef(props, 'xxx'));
    // 即使源 property 不存在，toRef 也会返回一个可用的 ref。这使得它在使用可选 prop 时特别有用，可选 prop 并不会被 toRefs 处理

    
    // --------------------------------------------------------
    
    // toRefs(用的比较多)
    // 将响应式对象转换为普通对象，其中结果对象的每个 property 都是指向原始对象相应 property 的 ref。

    // const state = reactive({
    //   name: '张三',
    //   age: 23
    // });

    // const plainObj = toRefs(state);
    // console.log(plainObj);
    // console.log(plainObj.name.value);
    // console.log({
    //   ...plainObj
    // });

    // console.log(isRef(plainObj)); // false
    // console.log(isRef(plainObj.name)); // true

    // return {
    //   // state
    //   // ...plainObj
    //   ...toRefs(state)
    // }
    
    // 和 toRef 一样也和原始的 property 链接 起来了

    // state.age = 33;
    // console.log(state.age); // 33
    // console.log(plainObj.age.value); // 33
    
    // plainObj.age.value = 18;
    // console.log(state.age); // 18
    // console.log(plainObj.age.value); // 18
    
    // --------------------------------------------------------

    // customRef
    // 创建一个自定义的 ref，并对其依赖项跟踪和更新触发进行显式控制
    // 它需要一个工厂函数，该函数接收 track 和 trigger 函数作为参数，
    // 并且应该返回一个带有 get 和 set 的对象。


    // const text = useDebounce('你好', 500);

    // return {
    //   text
    // }


    // --------------------------------------------------------

    // shallowRef (shallow 浅的)
    // 创建一个跟踪自身 .value 变化的 ref，但不会使其值也变成响应式的。

    // triggerRef
    // 手动执行与 shallowRef 关联的任何副作用。

    // const info = ref({
    //   name: '张三'
    // });
    const info = shallowRef({
      name: '张三'
    });

    // console.log(info);

    // info.value = {
    //   name: '李四'
    // }

    // console.log(info.value);
    // console.log(isReactive(info.value));
    // 使用ref时 给info.value 重新赋值 也同样是一个proxy
    // 而shallowRef则不会

    let name = '';
    watchEffect(() => {
      name = info.value.name;
      console.log(name); // shallowRef只会触发一次 值更改或删除时不触发

      // 通过 triggerRef 进行手动触发才行 triggerRef 一般搭配 shallowRef使用
    })

    info.value.name = '李四';

    triggerRef(info); // 手动触发一次

    // --------------------------------------------------------

    // readonly 被readonly包裹的数据可以是响应式的，返回的数据是只读的 也是深度的
    // const readonlyObj = readonly(proxyObj);
    // const readonlyRef = readonly(count);
    // console.log(readonlyRef.value);
    // readonlyRef.value = 666;
    // console.log(readonlyRef.value);

    // watchEffect(() => {
    //   console.log(readonlyObj.b.e.h.i);
    // })

    // proxyObj.b.e.h.i = 333;
    // readonlyObj.b.e.h.i = 333; // warning!!!

  }
}
</script>

<style>

</style>
